/*
重新建設 JDK 環境會發生編輯 JBuilder 指到的 alt-rt.jar (ex:jdk1.6.0_21) 發生 HashMap
版本混淆的問題，這時候把同一個目錄(C:\Program Files\Java\jdk1.6.0_21\jre\lib)下的
rt.jar 複製後改名 alt-rt.jar 即可解決！
*/

/*
2011-05-26
 1. fot debug 需求以 log4j 管理加入 DeBuger，由 DeBuger 整合控管訊息列印。
*/

package idv.jieh.commons.sql;

import java.sql.*;
import java.util.*;

import idv.jieh.commons.util.DeBuger;

public class TableCache extends LinkedList implements Row2TableChannel, Cloneable {

  private DeBuger dbr = new DeBuger();

  // 不想給空字串
  private String tableName = "";
  /**
   * <font color=red> [請勿使用...]</font>
   * @param idx 如果有 join
   * @return a
   */
  public String getTableName(int idx){
    return tableName;
  }
  // 刪除時設定索引值欄位名稱。

  /**
   * [施工中]更新資料內容。
   * @param conn Connection Object　
   * @return true
  public void reflash(Connection conn) throws SQLException{
  }

  private String mSql = "";
  public void rememberSQL(String sql) {
    mSql = sql;
  }
*/

  /**
   * 根據輸入的索引值刪除資料。<p>
   * 索引值起始為 1，也就是說如果輸入 2 就代表要刪除第 2 筆資料。刪除後這個方法將會傳回被
   * 刪除的 <code>RowCache</code>。
   * @param idx 要刪除目標資料的索引值。
   * @return 被刪除的 <code>RowCache</code>。
   */
  public RowCache delRow(int idx){
    idx--;
    keymap.remove(idx);
    RowCache r = (RowCache) super.remove(idx);
    return r;
  }

  public void delRow(String... idx){
    Vector delkey = new Vector();
    for(int i=0,l=idx.length; i<l; i++){
      delkey.addElement(idx[i]);
      int key = this.regenerate(delkey, Integer.parseInt(idx[i]));
      this.delRow(key);
    }
  }

  // 推算出欲刪除的資料目前的位置。
  private int regenerate(Vector delkey, int key){
    int r = key;
    for(
        int i=0, l=delkey.size()-1;
        i<l && Integer.parseInt(delkey.elementAt(i).toString()) < key;
        i++){
      r--;
    }
    return r;
  }

// *************************** 以上為施工區 ***************************

//  private HashMap keymap = new HashMap();
  // 存放索引值得對照順序。
  private LinkedList keymap = new LinkedList();
  // 存防欄位名稱的數量。
  private int colsize = 0;
  // 存放欄位名稱。
  private Vector columnNames;

  // 建構時設定主索引值欄位名稱。
  private Vector gi = new Vector(5, 1);
  // 查詢時設定索引值。
  private Vector qi = new Vector(5, 1);
  // 刪除時設定索引值。
  private Vector di = new Vector(5, 1);

  /**
   * 建構子。<p>
   */
  public TableCache() {}

  /**
   * 清除所有的內涵元件，並且還原相關索引值。
   */
  public void clear(){
    di.clear();
    qi.clear();
    keymap.clear();
    super.clear();
  }

  /**
   * 複製這個物件。<p>
   * @return 傳回這個物件的副本。
   */
  public Object clone(){
    //delkey.clear();
    di.clear();
    qi.clear();
    return super.clone();
  }

  /**
   * 設定刪除的索引值。<p>
   * 當呼叫 <u>delRow8Idx()</u> 方法之前，可以依據 <u>addGenerateIdxCol()</u> 所
   * 設定的欄位名稱順序，依序放入要刪除的欄位值，這樣就可以正確地刪除某筆
   * <code>RowCache</code>。
   * @param value 索引值。
   * @see TableCache#delRow8Idx()
   * @see TableCache#addGenerateIdxCol(String)
   */
  public void addDeleteIdx(String value) {
    di.addElement(value);
  }

  /**
   * 刪除指定的 Row。<p>
   * 需要透過 <u>addDeleteIdx()</u> 指定 column。
   * @see TableCache#addDeleteIdx(String)
   * @see TableCache#addGenerateIdxCol(String)
   * @return int 刪除成功的筆數。
   */
  public int delRow8Idx() {
    int l = di.size() - 1;
    StringBuffer key = new StringBuffer();
    for (int i = 0; i < l; i++) {
      key.append(di.elementAt(i));
      key.append("~jieh~");
    }
    key.append(di.lastElement());
    di.clear();
    int k = keymap.indexOf(key.toString());
    keymap.remove(k);
    super.remove(k);
    return ++k;

    /*
    Object idx = keymap.get(key.toString());
    if (idx == null) {
      // 沒有設定索引值。
    }
    delkey.add(idx);
    int k = this.regenerate(Integer.parseInt(idx.toString()));
     */
  }

  /**
   * 取得一筆 <code>RowCache</code>。<p>
   * 這個 <code>RowCache</code> 將建立一些屬於 <code>TableCache</code> 架構的資訊，
   * 這些資訊是讓 <code>RowCache</code> 被加入後可以正常運作的資訊，但是關於
   * <code>RowCache</code> 的資料內容必須由呼叫者自行建立，並且在建立的時候並未被正式加入
   * <code>TableCache</code> 必須呼叫 <code>insertRow()</code> 的方法之後才算完工。
   * @return RowCache Object。
   * @see TableCache#insert(RowCache)
   */
  public RowCache forInsert(){
    RowCache row = new RowCache(this);
    for(int i=0; i<colsize; i++){
      row.addElement("");
    }
    return row;
  }

  /**
   * 插入一筆 <code>RowCache</code> 到 <code>TableCache</code><p>
   * 這個 <code>RowCache</code> 將會被放置在 <code>TableCache</code> 的最後端。
   * 想取得合法的 <code>RowCache</code> 使用者必須由 forInsert() 取得。
   * @param row 已經建立好完整資料的 <code>RowCache</code>。
   * @see TableCache#forInsert()
   */
  public void insert(RowCache row){
//    keymap.put(this.mykey(row, gi.size()-1, gi), String.valueOf(super.size()));
    keymap.add(this.mykey(row, gi.size()-1, gi));
    super.add(row);
  }

  /**
   * 在任何指定的位置插入 RowCache。<p>
   * 使用者可以在任的指定的位置中插入一個 <code>RowCache</code>，但是這個指定的位置必須
   * 是合法的，必須介於 1 ~ TableCache 的最大值，也就是說使用者可以插在最前面和最後面。
   * @param idx 指定插入的位置，必須介於 1 ~ TableCache 的最大值。
   * @param row 要插入的 <code>RowCache</code>。
   * @see TableCache#forInsert()
   */
  public void insert(int idx, RowCache row){
    idx--;
    keymap.add(idx, mykey(row, gi.size()-1, gi));
    super.add(idx, row);
  }

  /**
   * 取得在 TableCache 中紀錄的主鍵對照表。<p>
   * 複製並傳回一份主鍵對照表。<br>
   * 由於是副本，在取得這個物件後，即使對該物件有任何修改都不會影響 TableCache 內的
   * 變化。<br>
   * @return HashMap 物件。
   */
  public HashMap getKeyMap(){
    return (HashMap)keymap.clone();
  }

  /**
   * 依據輸入的索引值直接取得 <code>RowCache</code>。<p>
   * 或許你可以使用 <code>get()</code>，但是這個方法可以讓你不必將物件轉型。
   * @param idx 指定取得存在於這個物件中的第幾筆資料(RowCache)。
   * @return RowCache Object。
   */
  public RowCache getRow(int idx){
    idx--;
    return (RowCache)get(idx);
  }

  /**
   * 設定主索引值欄位名稱。<p>
   * 這個方法必須在呼叫 <u>generate()</u> 方法之前使用，之後就沒有意義了。
   * 使用這個方法以後將可以幫助快速地刪除與查詢。
   * @param colname 索引值欄位名稱。
   * @see TableCache#generate(ResultSet)
   * @see TableCache#addQueryIdx(String)
   * @see TableCache#getRow8Idx()
   * @see TableCache#addDeleteIdx(String)
   * @see TableCache#delRow8Idx()
   */
  public void addGenerateIdxCol(Object colname) {
    gi.addElement(colname);
  }

  /**
   * 設定查詢時的索引值。<p>
   * 當呼叫 <u>getRow8Idx()</u> 方法之前，可以依據 <u>addGenerateIdxCol()</u> 所
   * 設定的欄位名稱順序，依序放入要查詢的欄位值，這樣就可以正確地抓取某筆
   * <code>RowCache</code>。
   * @param value 索引值。
   * @see TableCache#getRow8Idx()
   * @see TableCache#addGenerateIdxCol(String)
   */
  public void addQueryIdx(String value) {
    qi.addElement(value);
  }

  /**
   * 藉著之前索引值的設定取出一筆資料。<p>
   * 在呼叫 <u>generate()</u> 方法把資料庫資料分解成 <code>RowCache</code> 的結構之前，
   * 可以先利用 <u>addGenerateIdxCol()</u> 方法設定索引值。
   * 當要呼叫 <u>getRow8Idx()</u> 方法之前，必須先使用 <u>addQueryIdx()</u>
   * 把相對的索引值放入。這樣就可以正確地把指定的資料取出來了。<p>
   * @return 依據索引值傳回的資料。
   * @see TableCache#addQueryIdx(String)
   * @see TableCache#addGenerateIdxCol(String)
   */
  public RowCache getRow8Idx() {
    int l = qi.size() - 1;
    StringBuffer key = new StringBuffer();
    for (int i = 0; i < l; i++) {
      key.append(qi.elementAt(i));
      key.append("~jieh~");
    }
    key.append(qi.lastElement());
    qi.clear();
    int k = keymap.indexOf(key.toString());
    return k==-1?null:this.getRow(k+1);

    /*
    Object idx = keymap.get(key.toString());
    if (idx == null) {
      return null;
    }
    int k = this.regenerate(Integer.parseInt(idx.toString()));
    return (RowCache)super.get(Integer.parseInt(idx.toString()));
   */
  }

  /**
   * 取得所有的欄位名稱。<p>
   * @return 欄位名稱陣列。
   */
  public java.util.Vector getAllCols() {
    return this.columnNames;
  }

  /**
   * 取得欄位的長度。<p>
   * @return 欄位長度。
   */
  public int getColSize() {
    return colsize;
  }

  /**
   * 以輸入的欄位名稱取得該欄位名稱的排列位置。<p>
   * @param col 欄位名稱。
   * @return 索引值。
   */
  public int getColIdx(Object col) {
    String c = col.toString();
    int idx = columnNames.indexOf(c.toUpperCase());
    if (idx == -1) {
      throw idv.jieh.commons.util.MyRuntimeException.noSuchElement(c);
    }
    return idx;
  }

  /**
   * 取得索引值指定的欄位名稱。<p>
   * 依據輸入的索引摭取出被保存下來的欄位名稱。
   * @param idx 索引值。
   * @return 欄位名稱。
   */
  public String getColName(int idx) {
    return columnNames.elementAt(idx).toString();
  }

  /**
   * 以欄位名稱取得資料陣列。<p>
   * 依據輸入的欄位名稱，將在被保存在這個物件內，屬於該欄位的值整理成一個陣列後回傳。
   * @param col 欄位名稱索引值。
   * @return 一個符合條件的資料陣列。
   */
  public Vector getList8ColumnName(Object col) {
    Vector r = new Vector();
    ListIterator i = super.listIterator();
    while (i.hasNext()) {
      r.addElement(((RowCache) i.next()).getValue(col));
    }
    r.trimToSize();
    return r;
  }

  /**
   * 開始保存資料庫資料。<p>
   * 將由資料庫查詢出來的資料集傳入，<code>TableCache</code> 會將他轉換成其他資料格式後
   * 保存起來，接著使用者就可以藉由這個物件鐘各種不同的方法，來取出不同格式或型態的資料。
   * <br>
   * 在使用這個方法之前，可以先利用 <u>addGenerateIdxCol()</u> 方法設定索引值。
   * 這樣就可以搭配 <u>addQueryIdx()</u> 和 <u>getRow8Idx()</u> 這兩個方法，
   * 準確地把指定的資料取出來了。<p>
   * 詳細用法可以參考 <u>getRow8Idx()</u>。
   * @param rs 要被保存的資料庫資料集。
   * @throws SQLException
   * @see TableCache#addGenerateIdxCol(String)
   */
  public void generate(ResultSet rs)
      throws SQLException {

    columnNames = SimpleConnection.getColumn(rs);
    colsize = columnNames.size();
    tableName = rs.getMetaData().getTableName(1);

    for (int i=0, l=gi.size()-1; rs.next(); i++) {
      RowCache row = new RowCache(rs, this);
      keymap.add(mykey(row, l, gi));
      super.add(row);
    }
  }

  // 產生程式專屬的 key 值, 以紀錄 table 的索引位置。
  private String mykey(RowCache row, int l, Vector key) {
    if (key.isEmpty()) {
      return "";
    }
    StringBuffer r = new StringBuffer();
    for (int i = 0; i < l; i++) {
      r.append(row.getValue(key.elementAt(i).toString()));
      r.append("~jieh~");
    }
    r.append(row.getValue(key.lastElement().toString()));
    return r.toString();
  }

  /**
   * 直接呼叫 System.out.println() 將 TableCache 中每筆的資料內容印出。<p>
   * 印出的格式每筆如下：<br>
   * ROW 1 : [value1, value2, value3...]<br>
   * ROW 2 : [value1, value2, value3...]<br>
   * ROW 3 : [value1, value2, value3...]<br>
   *  .<br>
   *  .<br>
   *  .<br>
   */
  public void list() {
    java.util.Iterator it = this.iterator();
    int count = 0;
    while (it.hasNext()) {
      count++;
      RowCache row = (RowCache) it.next();
      dbr.debug("", "ROW " + count + " : " + row);
//      System.out.println("ROW " + count + " : " + row);
    }
  }


  /**
   * 將使用者指定欄位中的數值加總統計回傳。<P>
   * @param tc TableCache Object 要被計算的資料來源。
   * @param column String 該 TableCache 中要被計算的欄位名稱。
   * @return double 回傳計算後的結果。
   */
  public static double countColumn(TableCache tc, Object column) {
    java.util.Vector <String>list = tc.getList8ColumnName(column);
    double r = 0;
    for(String value : list) r += Double.parseDouble(value);
    return r;
  }


/*************************** 以下為測試區 ***************************
    public static void main(String[] args) throws SQLException{
      com.exentric.util.DeBuger dbr = new com.exentric.util.DeBuger("TableCache");
      StringBuffer sql = new StringBuffer("select * from epsys_menu_node");
      com.company.comm.UserOnline uo = new com.company.comm.UserOnline();
      java.sql.Connection conn = uo.getConnection();
      java.sql.Statement stmt = conn.createStatement();
      java.sql.ResultSet rs = stmt.executeQuery(sql.toString());

      TableCache me = new TableCache();
      me.addGenerateIdxCol("emn_name");
      me.addGenerateIdxCol("emn_root");
      me.addGenerateIdxCol("emn_parent");
      me.generate(rs);
      uo.freeConnection(conn);

      dbr.normal("Column Names : " + me.getAllCols());
      RowCache newrow = me.forInsert();
//      newrow.put("emn_id", "hashmap id");
//      newrow.put("emn_name", "hashmap name");
//      newrow.put("emn_root", "hashmap root");
//      newrow.put("emn_parent", "hashmap parent");

      //*
      newrow.setValue("yes myid", "emn_id");
      newrow.setValue("my new name", "emn_name");
      newrow.setValue("oh new root", "emn_root");
      newrow.setValue("dear my parent", "emn_parent");
      //
      dbr.normal("New Row : "+newrow);
      me.insert(newrow);

      // Test getRow8Idx() method
      me.addQueryIdx("my new name");
      me.addQueryIdx("oh new root");
      me.addQueryIdx("dear my parent");
      dbr.normal("new row : " + me.getRow8Idx());

      dbr.normal("Table Name : " + me.getTableName(1));
    }
//*/
}// end class
/*
  // 測試程式碼保留區

  // Test isEmpty() method
  dbr.normal("isEmpty is "+me.isEmpty());

  // Test iterator() method
  java.util.Iterator itr = me.iterator();
  while(itr.hasNext()){
    RowCache row = (RowCache)itr.next();
    dbr.normal(row.get(1).toString());
    dbr.normal(row.getValue("emn_name"));
  }

  // Test Column methods
  dbr.normal("Column Name : " + me.getColName(9));
  dbr.normal("Column Names : " + me.getAllCols());
  dbr.normal("Get List by Column Name : " + me.getList8ColumnName("emn_name"));

  // Test getRow8Idx() method
  me.addQueryIdx("Production Information");
  me.addQueryIdx("EMR20020619140101");
  me.addQueryIdx("foldersTree");
  dbr.normal("key Names : " + me.getRow8Idx().getValue("emn_name"));
*/
